package com.brayan.webapp.repository.realestate;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.ejb.Asynchronous;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.log4j.Logger;

import com.brayan.webapp.model.general.geoinfo.GeoInfo;
import com.brayan.webapp.model.realestate.Reservation;
import com.brayan.webapp.model.realestate.Reservation_;
import com.brayan.webapp.model.realestate.Tenant;
import com.brayan.webapp.model.realestate.Tenant_;
import com.brayan.webapp.model.realestate.pricemodel.DayFactor;
import com.brayan.webapp.model.realestate.pricemodel.WeekFactor;
import com.brayan.webapp.model.realestate.pricemodel.WeekFactor_;
import com.brayan.webapp.service.general.geoinfo.GeoIPBean;

@Stateless
public class ReservationRepository {

	// @Inject
	// private transient Logger LOGGER;
	// static final Logger LOGGER =
	// LogManager.getLogger(ReservationRepository.class.getName());
	private static Logger LOGGER = Logger.getLogger(ReservationRepository.class);

	@PersistenceContext(unitName = "ccms")
	EntityManager em;

	@Inject
	GeoIPBean geoIpService;

	public ReservationRepository() {
		// TODO Auto-generated constructor stub
	}

	public List<DayFactor> getAllDayFactors() {
		return null;
	}

	/** Put the passed list of weeks into the query */
	public List<WeekFactor> getWeeks(List<Integer> weeks) {

		CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
		CriteriaQuery<WeekFactor> criteriaQuery = criteriaBuilder.createQuery(WeekFactor.class);
		Root<WeekFactor> rootWeekFactor = criteriaQuery.from(WeekFactor.class);
		// Do a where and check ifn the weeks are in attributes weekNumber
		criteriaQuery.select(rootWeekFactor).where(rootWeekFactor.get(WeekFactor_.weekNumber).in(weeks));
		TypedQuery<WeekFactor> typedQuery = em.createQuery(criteriaQuery);
		List<WeekFactor> pubList = typedQuery.getResultList();

		return pubList;
	}

	/**
	 * Simple save operation. Not update!
	 * 
	 * @param reservation
	 * @return
	 */
	public Reservation saveReservation(Reservation reservation) {
		Reservation res = reservation;
		if (em.contains(reservation)) {
			LOGGER.debug("Reservation is still in persistent context. No need to invoke 'persist' for id "
					+ reservation.getId());
		} else {
			res = em.merge(reservation);
			LOGGER.info("Saved or updated a reservation (id: " + res.getId() + ").");
		}
		em.flush();
		return res;
	}

	public Reservation findReservationById(Long reservationId) {
		return em.find(Reservation.class, reservationId);
	}

	@Asynchronous
	public void addGeoInfoToReservation(Long reservationId, String ipAddress) {
		GeoInfo geoInfo = geoIpService.getGeoInfoByIp(ipAddress);
		Reservation reservation = findReservationById(reservationId);
		if (reservation != null) {
			reservation.setGeoInfo(geoInfo);
			em.merge(reservation);
			LOGGER.error("Saved geo information to reservation " + reservationId + ".");
		} else {
			LOGGER.error("Failed to save geo information because reservation object with ID " + reservationId
					+ " is not found.");
		}
		em.flush();
	}

	@Asynchronous
	public void addGeoInfoToReservation(Reservation reservation, String ipAddress) {
		GeoInfo geoInfo = geoIpService.getGeoInfoByIp(ipAddress);
		if (geoInfo != null) {			
			if (em.contains(reservation)) {
				reservation = findReservationById(reservation.getId());
				reservation.setGeoInfo(geoInfo);
				em.merge(reservation);
				LOGGER.debug("Reservation is still in persistent context. No need to invoke 'persist' for id "
						+ reservation.getId());
			} else {
				LOGGER.info("Reseration with id " + reservation.getId() + " is not in the database. Thus persist.");
				reservation.setGeoInfo(geoInfo);
				em.persist(reservation);
			}
			// could be the reason for a double entry in the reservation table.
			//em.flush();
		}
	}

	public List<Reservation> getOngoingReservations(Integer nRecords, boolean includePassedReservations) {
		List<Reservation> ongoingReservations = new LinkedList<Reservation>();
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Reservation> criteriaQuery = critBuilder.createQuery(Reservation.class);
		Root<Reservation> reservation = criteriaQuery.from(Reservation.class);
		if (!includePassedReservations) {
			criteriaQuery.select(reservation).where(
					critBuilder.greaterThanOrEqualTo(reservation.get(Reservation_.endDate), new Date()));
		}
		criteriaQuery.orderBy(critBuilder.asc(reservation.get(Reservation_.dateCreated)));
		if (nRecords == null || nRecords == 0)
			ongoingReservations = em.createQuery(criteriaQuery).getResultList();
		else
			ongoingReservations = em.createQuery(criteriaQuery).setMaxResults(nRecords).getResultList();
		return ongoingReservations;
	}

	public List<Reservation> getOngoingReservationsByName(String nameFilter, boolean includePassedReservations) {
		List<Reservation> ongoingReservations = new LinkedList<Reservation>();
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Reservation> criteriaQuery = critBuilder.createQuery(Reservation.class);
		Root<Reservation> reservation = criteriaQuery.from(Reservation.class);

		Predicate finalPredicate = null, orPredicate, givennamePredicate, surenamePredicate, emailPredicate;
		if (!includePassedReservations)
			finalPredicate = critBuilder.greaterThanOrEqualTo(reservation.get(Reservation_.endDate), new Date());

		// cb.equal(reservation.get("email"), email));
		criteriaQuery.orderBy(critBuilder.asc(reservation.get(Reservation_.dateCreated)));

		if (nameFilter == null || nameFilter.isEmpty()) {

		} else {
			Join<Reservation, Tenant> tenant = reservation.join(Reservation_.tenant);
			Expression<String> surenametPath = tenant.get(Tenant_.surname);
			Expression<String> givenNamePath = tenant.get(Tenant_.givenname);
			Expression<String> emailPath = tenant.get(Tenant_.email);
			String surenamePattern = nameFilter + "%";
			String givenNamePattern = nameFilter + "%";
			String emailPattern = "%" + nameFilter + "%";

			givennamePredicate = critBuilder.like(givenNamePath, givenNamePattern);
			surenamePredicate = critBuilder.like(surenametPath, surenamePattern);
			emailPredicate = critBuilder.like(emailPath, emailPattern);

			orPredicate = critBuilder.or(givennamePredicate, surenamePredicate, emailPredicate);
			if (!includePassedReservations)
				finalPredicate = critBuilder.and(finalPredicate, orPredicate);
			else
				finalPredicate = orPredicate;
		}
		criteriaQuery.select(reservation).where(finalPredicate);
		ongoingReservations = em.createQuery(criteriaQuery).getResultList();
		return ongoingReservations;
	}

	/**
	 * Return reservations by Tenant id
	 * 
	 * @param tenantId
	 * @return
	 */
	public List<Reservation> getReservationsByTenantId(Long tenantId) {
		List<Reservation> ongoingReservations = new LinkedList<Reservation>();

		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Reservation> criteriaQuery = critBuilder.createQuery(Reservation.class);
		Root<Reservation> reservation = criteriaQuery.from(Reservation.class);
		Join<Reservation, Tenant> tenant = reservation.join(Reservation_.tenant);

		criteriaQuery.where(critBuilder.equal(tenant.get(Tenant_.id), tenantId));

		criteriaQuery.orderBy(critBuilder.desc(reservation.get(Reservation_.dateCreated)));

		ongoingReservations = em.createQuery(criteriaQuery).getResultList();
		return ongoingReservations;
	}

	public Reservation getReservationsById(Long reservationId) {
		Reservation reservation = new Reservation();

		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Reservation> criteriaQuery = critBuilder.createQuery(Reservation.class);
		Root<Reservation> reservationRoot = criteriaQuery.from(Reservation.class);
		criteriaQuery.where(critBuilder.equal(reservationRoot.get(Reservation_.id), reservationId));

		reservation = em.createQuery(criteriaQuery).getSingleResult();
		return reservation;
	}

	public void deleteReservation(Reservation reservation) {
		LOGGER.warn("Deleting reservation from Visitor " + reservation.getTenant().getGivenname() + " "
				+ reservation.getTenant().getSurname() + " with email " + reservation.getTenant().getEmail());
		// If it exists, delte. If not, merge and then delete.
		boolean isExisting = em.contains(reservation);
		if(isExisting)
			em.remove(reservation);
		else{
			reservation = findReservationById(reservation.getId());
			em.remove(reservation);
		}
	}
}
